package net.micode.notes.tool;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;


public class EncryptionUtil {
    private static final Logger LOGGER = Logger.getLogger(EncryptionUtil.class.getName());
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH = 128;
    private static final int GCM_IV_LENGTH = 12;


    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
        keyGen.init(256);
        return keyGen.generateKey();
    }


    public static String encodeKeyToBase64(SecretKey key) {
        return Base64.getEncoder().encodeToString(key.getEncoded());
    }


    public static SecretKey decodeKeyFromBase64(String base64Key) {
        byte[] decodedKey = Base64.getDecoder().decode(base64Key);
        return new SecretKeySpec(decodedKey, 0, decodedKey.length, ALGORITHM);
    }


    public static String encrypt(String plainText, SecretKey key) throws Exception {
        byte[] iv = new byte[GCM_IV_LENGTH];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);

        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);

        byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));


        byte[] result = new byte[iv.length + encryptedBytes.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, result, iv.length, encryptedBytes.length);

        return Base64.getEncoder().encodeToString(result);
    }


    public static String decrypt(String encryptedText, SecretKey key) throws Exception {
        byte[] encodedBytes = Base64.getDecoder().decode(encryptedText);

        byte[] iv = new byte[GCM_IV_LENGTH];
        byte[] encryptedBytes = new byte[encodedBytes.length - iv.length];
        System.arraycopy(encodedBytes, 0, iv, 0, iv.length);
        System.arraycopy(encodedBytes, iv.length, encryptedBytes, 0, encryptedBytes.length);

        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);

        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }


    public static void printKeyDetails(SecretKey key) {
        LOGGER.info("Algorithm: " + key.getAlgorithm());
        LOGGER.info("Encoded Key: " + Base64.getEncoder().encodeToString(key.getEncoded()));
    }


    public static boolean isValidKey(SecretKey key) {
        return ALGORITHM.equals(key.getAlgorithm());
    }


    public static String encryptSafe(String plainText, SecretKey key) {
        try {
            return encrypt(plainText, key);
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Encryption failed", e);
            return null;
        }
    }


    public static String decryptSafe(String encryptedText, SecretKey key) {
        try {
            return decrypt(encryptedText, key);
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Decryption failed", e);
            return null;
        }
    }
}